using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Runtime.Serialization;

using KLATencor.CommonApp.Infrastructure.StateMachine;

using Syncfusion.Windows.Forms.Diagram;
using Syncfusion.Windows.Forms.Diagram.Controls;

using StateMachine_Graphical_Editor.Shapes;
using StateMachine_Graphical_Editor.Utils;
using StateMachine_Graphical_Editor.Utils.Forms_RunTime_Managment;
using StateMachine_Graphical_Editor.Graphics_Rendering;

namespace StateMachine_Graphical_Editor.Forms
{
    /// <summary>
    /// An abstract form for displaying and tracking state machine elements.
    /// <para>Note, do not create instance from this class but use its inheriting forms!</para>
    /// </summary>
    [Serializable]
    public abstract partial class StatesForm : Form, IFormManagment, ISerializable
    {
        #region Members:

        /// <summary>
        /// interface to handle all the graphic rendering.
        /// </summary>
        protected Initializer initializer;
        
        /// <summary>
        /// The current machine which the editor handles.
        /// </summary>
        protected StateMachine machine;
        
        /// <summary>
        /// A collection of the logic sub states of the state this form represents. 
        /// </summary>
        protected StateCollection levelStatesCollection;
        
        /// <summary>
        /// Collection of all elements rendered on this form.
        /// </summary>
        protected NodeCollection diagramElements;
        
        /// <summary>
        /// The current graphical state on the form.
        /// </summary>
        protected GState gCurrent;
        
        /// <summary>
        /// the Form Parent state. 
        /// </summary>
        protected State mainState = null;
        
        /// <summary>
        /// The last graphical transition occured on the form. 
        /// </summary>
        protected GTransition last;
        
        /// <summary>
        /// Defines the mode this form is in: view only or editing
        /// </summary>
        protected DisplayModes displayMode;
        
        /// <summary>
        /// Set to true if any changes were made from last saving
        /// </summary>
        protected bool dirty = false;

        #endregion

        #region C'tor:

        public StatesForm()
        {
            InitializeComponent();
        }

        #endregion

        #region Getters/Setters:

        public GState CurrentGState
        {
            get { return gCurrent; }
            set
            {   //check if another stat is already current - happens when 
                //main state transition occurs from parent to one of its direct children
                if (gCurrent != null)
                {
                    gCurrent.CurrentState = false;
                }
                gCurrent = value; 
            }
        }

        public GTransition LastTransition
        {
            get { return last; }
            set { this.last = value; }
        }

        public Diagram Diagram
        {
            get { return this.diagram; }
        }

        public GState getGraphicalState(State state)
        {
            return initializer.getGraphicalState(state);
        }

        #endregion

        #region Controls Event Handlers:

        protected void addEvent_Click(object sender, EventArgs e)
        {
            try
            {
                //will throw exception if machine isn't running:
                string eventName = (String)this.eventDropDownBox.SelectedItem;
                eventsQueue.Items.Add(eventName);
                machine.QueueEvent(new KLATencor.CommonApp.Infrastructure.StateMachine.StateMachineEvent(eventName));
            }
            catch (Exception SMexception)
            {
                MessageBox.Show(SMexception.Message);
            }
        }

        #region Diagram Related Events:

        private void diagram_KeyDown(object sender, KeyEventArgs e)
        {
            // check for ctrl + 'Z':
            if (e.Control == true && e.KeyCode == Keys.Z)
            {
                //TODO: check what happens for duplicate transitions etc.
                this.diagram.Model.HistoryManager.Undo();
            }
            // check for ctrl+'Y':
            if (e.Control == true && e.KeyCode == Keys.Y)
            {
                this.diagram.Model.HistoryManager.Redo();
            }

            if (e.KeyCode == Keys.Delete)
            {
                //this key causes problems, so ignore the action
                this.diagram.Model.HistoryManager.Undo();
            }
        }

        private void diagram_DragDrop(object sender, DragEventArgs e)
        {
            Node dropedShape = diagram.Controller.SelectionList[0];
            Node newShape = null;
            if (dropedShape is RoundRect) //supposed to be a GState
            {
                newShape = new GState((RoundRect)dropedShape, this);
                long nextID = getNextID();
                ((GState)newShape).ID = nextID;
                ((GState)newShape).Name = "new State" + nextID;
            }
            else if (dropedShape is LineConnector) //supposed to be GTransition
            {
                newShape = new GTransition((LineConnector)dropedShape);
            }
            else if (dropedShape is Group) //supposed to be final mark
            {
                newShape = new GFinal((Group)dropedShape);
            }
            else if (dropedShape is Ellipse) //supposed to be an initial mark
            {
                newShape = new GInitial((Ellipse)dropedShape);
            }
            //plant the new shape
            diagram.Model.AppendChild(newShape);
            //remove the shape dropped from palette:
            diagram.Model.RemoveChild(dropedShape);
            //mark the new shape as selected:
            diagram.Controller.SelectionList.Add(newShape);
            diagramElements.Add(newShape);
            if (newShape is GState)
            {
                GState newState = newShape as GState;
                //popup a stateProperties From:
                StateProperties stateProperties = new StateProperties(newState, machine, mainState);
                stateProperties.ShowDialog();
                
                if (newState.IsInitialState)
                    initializer.switchInitial(newState);
            }

        }

        private void diagram_MouseUp(object sender, MouseEventArgs e)
        {
            //if in display mode - return immidiatly:
            if (this.displayMode == DisplayModes.VIEW_MODE)
                return;
            // check for right click:
            if (e.Button == MouseButtons.Right)
            {
                //clear the selection list:
                diagram.Controller.SelectionList.Clear();
                //checked for clicking on GState:
                if (diagram.Controller.NodesHit.Count > 0 && diagram.Controller.NodesHit[0] is GState)
                {
                    GState selectedState = diagram.Controller.NodesHit[0] as GState;
                    diagram.Controller.SelectionList.Add(selectedState);
                    this.setAsInitialStateToolStripMenuItem.Enabled = !selectedState.IsInitialState;
                    contextMenuStripGState.Show(this.diagram, new Point(e.X, e.Y));
                }

                else if (diagram.Controller.NodesHit.Count > 0 && diagram.Controller.NodesHit[0] is GTransition)
                {
                    GTransition selectedTransition = diagram.Controller.NodesHit[0] as GTransition;
                    // check if the transition is from initial symbol, or to final symbol with no other details:
                    if ((selectedTransition.FromNode is GInitial && selectedTransition.LogicTransition==null) ||
                          selectedTransition.ToNode is GFinal)
                        return;
                    diagram.Controller.SelectionList.Add(selectedTransition);

                    contextMenuStripGTransition.Show(this.diagram, new Point(e.X, e.Y));
                }
            }
        }

        #endregion

        #region GState Context Menu EventHandlers:

        private void editToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            GState selectedState = diagram.Controller.SelectionList[0] as GState;
            if (selectedState != null)
            {
                // 1. create a state properties form with the current state properties
                StateProperties properties = new StateProperties(selectedState, machine,mainState);
                // 2. set the new form as modal=>must close it in order to get to other forms
                properties.EditWanted();    //set dirty = false for no double entries;
                properties.ShowDialog(this);
                
                if (selectedState.IsInitialState)
                    initializer.switchInitial(selectedState);
            }
        }

        private void showSubStatesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            GState selected = diagram.Controller.SelectionList[0] as GState;
            SubStatesForm subStateForm = new SubStatesForm(selected.LogicState, DisplayModes.EDIT_MODE);

            subStateForm.ShowDialog(this);
        }

        private void setAsInitialStateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            GState selected = diagram.Controller.SelectionList[0] as GState;
            selected.IsInitialState = true;
            initializer.switchInitial(selected);
        }

        private void addSubStatesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            GState selectedState = diagram.Controller.NodesHit[0] as GState;
            SubStatesForm subStatesForm = new SubStatesForm(selectedState.LogicState, DisplayModes.EDIT_MODE);
            subStatesForm.ShowDialog();
        }

        private void addTransGstateToolStripMenu_Click(object sender, EventArgs e)
        {
            GState selectedState = diagram.Controller.SelectionList[0] as GState;
            TransionProperties addTrans = new TransionProperties(model, initializer, machine);
            addTrans.setNewTransitionProperties(selectedState, levelStatesCollection, diagramElements);
            addTrans.ShowDialog(this);
        }

        private void deleteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //GState selectedState = diagram.Controller.SelectionList[0] as GState;
            GState selectedState = diagram.Controller.NodesHit[0] as GState;

            deleteState(selectedState);
        }

        #endregion

        #region GTransition Context Menu EventHandlers:

        private void editGtransionToolStripMenu_Click(object sender, EventArgs e)
        {
            // 1. create a transition properties form with the current transition properties
            // 2. load it where the main form is its owner

            GTransition selectedTrans = diagram.Controller.SelectionList[0] as GTransition;
            TransionProperties editTrans = new TransionProperties(model, initializer, machine);
            //get transtion properties from diagram
            editTrans.setExistingTransitionProperties(selectedTrans, levelStatesCollection, diagramElements);
            editTrans.ShowDialog(this);
        }

        private void deleteToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            GTransition selectedTrans = diagram.Controller.SelectionList[0] as GTransition;
            //check if multiple transitions
            if (selectedTrans.MultipleTransitions)
            {
                //1. popup a selector of the transitions to be removed
                //2. update the name of the multiple transition
                DeleteTransitionsSelector deleteTransForm = new DeleteTransitionsSelector(selectedTrans.DisplayNamesList);
                deleteTransForm.ShowDialog();
                if (selectedTrans.DisplayNamesList.NumOfTrans == 1) //only1 remained
                {
                    selectedTrans.MultipleTransitions = false;
                }
                else if (selectedTrans.DisplayNamesList.NumOfTrans == 0) //remove from board
                {
                    model.RemoveChild(selectedTrans);
                    selectedTrans = null;
                }
                else
                {
                    //do nothing - still rpresents multiple transitions
                }
            }
            else //GTransition represents a single logic transition
            {
                //TODO - remove logic transition from TransitionCollection
                model.RemoveChild(selectedTrans);
                selectedTrans = null;
            }
            
            return;
        }

        #endregion

        #endregion

        #region IFormManagment implementation:

        public virtual/*abstract*/ string getName()
        {
            return null;
        }

        public virtual/*abstract*/ State getState()
        {
            return null;
        }

        public void setCurrentState(State current) 
        {
            if (gCurrent != null && gCurrent.LogicState.Equals(current))
            {
                gCurrent.CurrentState = true;
                return;
            }

            if (gCurrent != null && !gCurrent.LogicState.Equals(current))
                gCurrent.CurrentState = false;

            foreach (Node node in diagramElements)
            {
                GState tmp = node as GState;
                if (tmp != null && tmp.LogicState.Equals(current))
                {
                    tmp.CurrentState = true; //set the new one as current
                    return;
                }
            }
        }

        public void setLastTransition(Transition current)
        {
            //find the GTRansition which connects the NODES with the appropriate name 
            GTransition trans = initializer.getGraphicalTransition(current);
            if (last != null && last.Equals(trans))
            {
                last.LastTransition = true;
                return;
            }
            if (last != null && !last.Equals(trans))
            {
                last.LastTransition = false;
            }

            trans.LastTransition = true;
            last = trans;
        }

        public void showForm()
        {
            Show();
        }

        public void closeForm()
        {
            Close();
        }

        public void activateForm()
        {
            Activate();
        }

        public virtual/*abstract*/ void permitEventLaunching(bool isPermitted)
        {
            
        }

        #endregion

        #region Serializable Implementation:

        public StatesForm(SerializationInfo info, StreamingContext str)
            : base()
        {
        }

        public virtual void GetObjectData(SerializationInfo info, StreamingContext str)
        {
        }

        #endregion

        #region Private Methods: 

        /// <summary>
        /// Update the events list on the simulator or runt time list 
        /// </summary>
        protected virtual void updateEventsList()
        {
            levelStatesCollection = initializer.getLevelStates();
            if (levelStatesCollection != null)
            {
                foreach (State s in levelStatesCollection)
                {
                    foreach (Transition curTrans in s.Transitions)
                    {
                        String eventName = curTrans.EventName;
                        eventDropDownBox.Items.Add(eventName);
                    }
                }
            }
            if (eventDropDownBox.Items.Count > 0 && displayMode == DisplayModes.VIEW_MODE)
                addEvent.Enabled = true;
        }

        //to handle .NET issues of invoking GUI out side the GUI thread
        public delegate void StateChangeDelegate(object sender, KLATencor.CommonApp.Infrastructure.StateMachine.StateChangeEventArgs args);

        /// <summary>
        /// Handles the event of performing a transition in the State Machine
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public void onTransitionOccured(object sender, KLATencor.CommonApp.Infrastructure.StateMachine.StateChangeEventArgs args)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new StateChangeDelegate(onTransitionOccured), new object[] { sender, args });

            else
            {
                //event is captured in all forms, but to be handled only on the top one
                if (FormsManager.getManager().isTopForm(this))
                {
                    foreach (Node node in diagramElements)
                    {
                        if (node is GState)
                        {
                            GState tmp = node as GState;
                            tmp.handleEventForGState(args);
                        }
                        else if (node is GTransition)
                        {
                            GTransition tmp = node as GTransition;
                            tmp.handleEventForGTransition(args);
                        }
                        else if (node is OuterState)
                        {
                            OuterState tmp = node as OuterState;
                            tmp.handleEventForOuterState(args);
                        }

                    }
                }
            }
        }

        /// <summary>
        /// returns the max stateId existing in the machine
        /// </summary>
        /// <returns></returns>
        public long getNextID()
        {
            long maxId = 0;
            foreach (State st in machine.AllStates)
            {
                maxId = Math.Max(maxId, st.ID);
            }
            return (++maxId);
        }

        /// <summary>
        /// Set an alternate initial state instead of the current one.
        /// Pick the first available state on diagram
        /// </summary>
        private void setAlternateInitial()
        {
            //find the first state which is not the parent, and set it to be the initial
            for (int i = 0; i < model.ChildCount; i++)
            {
                GState state2BeInitial = model.GetChild(i) as GState;

                if (state2BeInitial != null && state2BeInitial.Visible)
                {
                    state2BeInitial.IsInitialState = true;
                    initializer.switchInitial(state2BeInitial);
                    break;
                }
            }
        }

        /// <summary>
        /// Counts how man states are on the diagram
        /// </summary>
        /// <returns>The amount of states</returns>
        private int countModelStates()
        {
            int stateCounter = 0;

            for (int i = 0; i < model.ChildCount; i++)
            {
                if (model.GetChild(i) is GState && model.GetChild(i).Visible)
                    stateCounter++;
            }
            return stateCounter;
        }

        /// <summary>
        /// removes a selected state from the diagram
        /// </summary>
        /// <param name="selectedState">the state to remove</param>
        private void deleteState(GState selectedState)
        {

            if (selectedState == null)
                return;

            //do not allow 0 states on board
            if (countModelStates() < 2) //1 is the main, and the other is the only one displayed
            {
                MessageBox.Show(
                    "At least 1 state must be present on board",
                    "abort delete",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
                return;
            }

            //TODO - remove state from State Collection in the proper parent State        
            diagram.Model.RemoveChild(selectedState);

            if (selectedState.IsInitialState)
            {
                setAlternateInitial();
            }

            removeRelatedElements(selectedState);


        }

        /// <summary>
        /// Removes all related elements to the state that was removed
        /// </summary>
        /// <param name="state"></param>
        private void removeRelatedElements(GState state)
        {
            NodeCollection elementsToRemove = new NodeCollection();
            for (int i = 0; i < model.ChildCount; i++)
            {
                GTransition toCheck = model.GetChild(i) as GTransition;
                if (toCheck != null )
                {
                    //in going transitions
                    if (toCheck.ToNode == null)
                        elementsToRemove.Add(toCheck);

                    //out going transitions
                    else if (toCheck.FromNode == null)
                    {
                        elementsToRemove.Add(toCheck);
                        //cjeck if element is GFinal and doesn't have more connections to other elements
                        GFinal finalToCheck = toCheck.ToNode as GFinal;
                        if (finalToCheck != null && finalIsLonely(finalToCheck))
                        {
                            elementsToRemove.Add(finalToCheck);
                        }
                    }
                }
            }

            //remove the selcted elements
            model.RemoveRange(elementsToRemove);
        }

        /// <summary>
        /// check if to remove the final state specified
        /// </summary>
        /// <param name="finalToCheck"></param>
        /// <returns></returns>
        private bool finalIsLonely(GFinal finalToCheck)
        {
            int connectorsCount = 0;
            for (int i = 0; i < model.ChildCount; i++)
            {
                GTransition gTransition = model.GetChild(i) as GTransition;
                if (gTransition != null && finalToCheck.Equals(gTransition.FromNode))
                    connectorsCount++;
            }
            return connectorsCount < 2; //more than one (the one that caused this check)
        }

        private void StatesForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (machine != null)
                this.machine.OnStateChange -= new StateMachine.StateChange(onTransitionOccured);
        }

        #endregion

    }
}